package nio.nio_chat;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @author charwayH
 * 目标:服务端群聊系统
 */
@SuppressWarnings("all")
public class Server {
    // 1.定义一些成员属性:选择器、服务端通道、端口
    private Selector selector;
    private ServerSocketChannel ssChannel;
    private static final int PORT = 9999;

    // 2.定义初始化逻辑代码
    public Server() {
        try {
            // a.创建选择器对象
            selector = Selector.open();
            // b.获取通道
            ssChannel = ServerSocketChannel.open();
            // c.绑定客户端连接的端口
            ssChannel.bind(new InetSocketAddress(PORT));
            // d.设置非阻塞通信模型
            ssChannel.configureBlocking(false);
            // e.把通道注册到选择器上，并且开始制定接收连事件
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // 创建服务端对象
        Server server = new Server();
        // 开始监听客户端的各种消息事件：连接、群聊、离线消息
        server.listen();
    }

    /**
     * 开始监听
     */
    private void listen() {
        try {
            while (selector.select() > 0) {
                // a.获取选择器中所有注册通道的就绪时间
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                // b.开始遍历这些事件
                while (iterator.hasNext()) {
                    // 提取这个事件
                    SelectionKey sk = iterator.next();
                    if (sk.isAcceptable()) {
                        // 客户端接入请求
                        // 获取当前客户端通道
                        SocketChannel sChannel = ssChannel.accept();
                        // 注册成非阻塞模式
                        sChannel.configureBlocking(false);
                        // 注册给选择器,监听读取数据的事件
                        sChannel.register(selector, SelectionKey.OP_READ);
                    } else if (sk.isReadable()) {
                        // 处理这个客户端的消息，然后实现转发逻辑
                        readClientData(sk);
                    }
                    // 处理完毕后需要移除当前事件
                    iterator.remove();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 接收当前客户端信息，转发给其他客户端通道
     */
    private void readClientData(SelectionKey sk) {
        SocketChannel sChannel = null;
        try {
            // 直接得到当前客户端通道
            sChannel = (SocketChannel) sk.channel();
            // 创建缓冲区对象开始接收客户端的数据
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count = sChannel.read(buffer);
            if (count > 0) {
                buffer.flip();
                // 提取读取到的信息
                String msg = new String(buffer.array(), 0, buffer.remaining());
                System.out.println("接收到的客户端消息" + msg);
                // 把这个消息推给全部的客户端接收
                sendMsgToAllClient(msg, sChannel);
            }
        } catch (Exception e) {
            try {
                System.out.println("有人离线了" + sChannel.getRemoteAddress());
                // 当前客户端离线
                sk.cancel();
                sChannel.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
    }

    /**
     * 把当前客户端消息推送给所有在线注册的channel
     * @param msg
     * @param sChannel
     */
    private void sendMsgToAllClient(String msg, SocketChannel sChannel) throws IOException {
        System.out.println("服务端开始转发消息:当先处理的线程" + Thread.currentThread().getName());
        for (SelectionKey key : selector.keys()) {
            Channel channel = key.channel();
            // 不要把数据发给自己
            if(channel instanceof SocketChannel && sChannel != channel){
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                ((SocketChannel)channel).write(buffer);
            }
        }
    }

}
